﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using TradeHelp.Utilities;
using Wintellect.PowerCollections;

namespace TradeHelp.MaterializedObjectViews
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class TableView<T> : View<T>
    {
        private readonly Set<T> _cache = new Set<T>();
        private int _cacheVersion;
        private StreamMessageDelegate<T> _callbacks;

        /// <summary>
        /// Incrementing number represents the change version of the data
        /// contained within the view.
        /// </summary>
        /// <returns></returns>
        public override int GetVersion()
        {
            lock (_cache)
            {
                return _cacheVersion;
            }
        }

        /// <summary>
        /// Requests an read only snapshot of the data contained within the view.
        /// </summary>
        /// <returns>collection of unchanging data</returns>
        public override ViewData<T> GetData()
        {
            lock (_cache)
            {
                return new ViewData<T>(_cache.ToList(), _cacheVersion);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        public void Process(ViewMessage<T> message)
        {
            int sequenceNum;
            List<ViewMessage<T>> data;
            StreamMessageDelegate<T> callbacks = null;

            lock (_cache)
            {
                ViewMessage<T> addMessage;
                if (!TryProcess(message, out addMessage)) return;
                data = new List<ViewMessage<T>> { addMessage };
                sequenceNum = ++_cacheVersion;
                callbacks += _callbacks;
            }

            if (callbacks != null)
            {
                callbacks(new ViewMessageBurst<T>(data, sequenceNum));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="burst"></param>
        public void Process(ViewMessageBurst<T> burst)
        {
            Invariant.CheckIsNull(burst, "burst");
            if (burst.Count == 0) return;

            int sequenceNum;
            List<ViewMessage<T>> data = null;
            StreamMessageDelegate<T> callbacks = null;

            lock (_cache)
            {
                foreach (var msg in burst)
                {
                    ViewMessage<T> addMessage;
                    if (!TryProcess(msg, out addMessage)) continue;
                    data = data ?? new List<ViewMessage<T>>();
                    data.Add(addMessage);
                }

                if (data == null) return;
                sequenceNum = ++_cacheVersion;
                callbacks += _callbacks;
            }

            if (callbacks != null)
            {
                callbacks(new ViewMessageBurst<T>(data, sequenceNum));
            }
        }

        public bool TryProcess(ViewMessage<T> viewMessage, out ViewMessage<T> retMessage)
        {
            retMessage = default(ViewMessage<T>);
            if (viewMessage.Action == ViewAction.Remove)
            {
                T oldData;
                if (_cache.TryGetItem(viewMessage.PrevData, out oldData))
                {
                    _cache.Remove(oldData);
                    retMessage = ViewMessage<T>.CreateRemove(oldData);
                    return true;
                }
                return false;
            }
            if (viewMessage.Action == ViewAction.Add || viewMessage.Action == ViewAction.Update)
            {
                T oldData;
                T newData = viewMessage.Data;
                if (_cache.TryGetItem(newData, out oldData))
                {
                    var viewData = newData as IViewEquivalent<T>;
                    if (viewData != null && viewData.IsViewEquivalentTo(oldData))
                    {
                        return false;
                    }
                    retMessage = ViewMessage<T>.CreateUpdate(newData, oldData);
                }
                else
                {
                    retMessage = ViewMessage<T>.CreateAdd(newData);
                }
                _cache.Add(newData);
                return true;
            }
            throw new NotImplementedException();
        }

        /// <summary>
        /// Request to stream data after creation.  All incremental updates are
        /// provided to the callback.  StreamData can have the additional side effect
        /// of instructing the stream to initialize and begin streaming to itself.
        /// </summary>
        /// <param name="callback">delegate to handle incremental data message bursts</param>
        public override void StreamData(StreamMessageDelegate<T> callback)
        {
            Invariant.CheckIsNull(callback, "callback");

            ViewMessageBurst<T> data;
            lock (_cache)
            {
                _callbacks += callback;
                data = new ViewMessageBurst<T>(_cache.Select(d => ViewMessage<T>.CreateAdd(d)).ToList(), _cacheVersion, true);
            }
            ThreadPool.QueueUserWorkItem(state => callback(data));
        }

        /// <summary>
        /// Request to unsubscribe client code from recieving further incremental updates
        /// </summary>
        /// <param name="callback"></param>
        public override void Unsubscribe(StreamMessageDelegate<T> callback)
        {
            lock (_cache)
            {
                _callbacks -= callback;
            }
        }
    }
}